#pragma once

#include "Misc/EngineEditorMacros.h"
#include "Framework/EditorModelSubsystem.h"
#include "Containers/ScriptDelegateImplement.h"
#include "Containers/ThreadSafePtr.h"
#include "Containers/Tuple.h"
#include "Framework/Tickable.h"
#include "EditorScriptDebugSubsystem.m.h"

class CTCPClient;
class CJsonObjectNode;
class RProcess;

RCLASS()
class REditorBreakpoint : public RObject
{
    ROBJECT

public:
    
    RFIELD()
    CString FileId;

    RFIELD()
    int64_t LineIndex;

    RFIELD()
    bool bEnabled;
    
};


RSTRUCT()
struct SEditorCallStackFrame
{
    ROBJECT


    RFIELD()
    CString FunctionName;

    RFIELD()
    CString FileName;

    RFIELD()
    int64_t LineNumber = 0;
};

RSTRUCT()
struct SEditorScriptValueReference
{
    ROBJECT

    RFIELD()
    CString TypeName;

    RFIELD()
    CString Address; //address of the value

    bool operator==(const SEditorScriptValueReference & InOther) const
    {
        return TypeName == InOther.TypeName && Address == InOther.Address;
    }

    uint64_t GetHashCode() const
    {
        return WH::HashCombine(TypeName.GetHashCode(), Address.GetHashCode());
    }
};

RSTRUCT()
struct SEditorScriptValue
{
    ROBJECT

    RFIELD()
    SEditorScriptValueReference Reference;

    RFIELD()
    CString Value; //value in string

    RFIELD()
    CString Name; //name of the value

    RFIELD()
    int64_t NamedChildNum = 0;

    RFIELD()
    int64_t IndexedChildNum = 0; 

    bool HasChildren() const
    {
        return NamedChildNum > 0 || IndexedChildNum > 0;
    }
};

struct SEditorScriptFrameData
{
    TVector<SEditorScriptValue> Parameters;

    TVector<SEditorScriptValue> LocalVariables;
};


enum class EDebugClientState
{
    NotRunning,
    Initializing, //client is connected , but not ready
    Connected, //server is connected and running
    Paused, //server is paused
    //Disconnecting, //client is disconnecting, but not yet disconnected
};

RCLASS()
class REditorScriptDebugSubsystem : public REditorModelSubsystem  , public ITickable
{
    ROBJECT

public:

    void AddBreakpoint(const CString & InFileId, int64_t InLineIndex);
    void RemoveBreakpoint(const CString & InFileId, int64_t InLineIndex);
    //if no breakpoint, add it, else remove it
    void AddOrRemoveBreakpoint(const CString & InFileId, int64_t InLineIndex);
    
    RFIELD()
    TScriptDelegate<void(const CString & InFileId, int64_t InLineIndex)> OnBreakpointAdded;
    RFIELD()
    TScriptDelegate<void(const CString & InFileId, int64_t InLineIndex)> OnBreakpointRemoved;


    void DisableBreakpoint(const CString & InFileId, int64_t InLineIndex);
    void EnableBreakpoint(const CString & InFileId, int64_t InLineIndex);
    void ToggleBreakpoint(const CString & InFileId, int64_t InLineIndex);

    //if no breakpoint, return empty 
    //else return the is enabled
    TOptional<bool> GetBreakpointType(const CString & InFileId, int64_t InLineIndex) const;

    //for breakpoint ability
    int64_t GetBreakpointNumber() const;
    bool IsBreakpointEnabled(int64_t InBreakpointIndex) const;
    void TogglerBreakpointEnabled(int64_t InBreakpointIndex);
    CString GetBreakpointFileName(int64_t InBreakpointIndex) const; 
    CString GetBreakpointFilePath(int64_t InBreakpointIndex) const;
    int64_t GetBreakpointLine(int64_t InBreakpointIndex) const;
    void RemoveBreakpoint(int64_t InBreakpointIndex);


    //get all file id which has breakpoint
    TVector<CString /*file ID*/ > GetBreakpointFileList() const;
    //get all breakpoint in the file
    TVector<TSharedObjectPtr<REditorBreakpoint>> GetBreakpointLineList(const CString & InFileId) const;

    //update all breakpoints in the file
    void SetBreakpoints(
        CString InFileId,
        const TVector<TTuple<int64_t,bool>> & InBreakpointList);

protected:
    //all breakpoints for display
    RFIELD()
    TVector<TSharedObjectPtr<REditorBreakpoint>> BreakpointList;
    
    //all breakpoints for search
    RFIELD()
    TMap<CString /*file id, generally is file path */
        ,TMap<int64_t //line index
            ,TSharedObjectPtr<REditorBreakpoint> /* breakpoint */>  > BreakpointMap;



//call stack:
public:
    //the first frame is the newest
    void SetCallStack(const TVector<SEditorCallStackFrame>& InCallStack);

    RFIELD()
    TScriptDelegate<void()> OnCallStackChanged;
    
    void SelectFrame(int64_t InFrameIndex);
    //return -1 if selected nothing 
    int64_t GetSelectedFrame() const;

    int64_t GetCallStackFrameNum() const;
    CString GetCallStackFrameFunctionName(int64_t InIndex) const;
    CString GetCallStackFrameFileName(int64_t InIndex) const;
    int64_t GetCallStackFrameLineNumber(int64_t InIndex) const;

protected:
    TVector<SEditorCallStackFrame> CallStack;
    int64_t SelectedFrame=-1;

//selected frame data
public:
    void SetSelectedFrameData(const SEditorScriptFrameData & InFrameData);

    RFIELD()
    TScriptDelegate<void()> OnSelectedFrameDataChanged;

    int64_t GetSelectedFrameDataParameterNum() const;
    CString GetSelectedFrameDataParameterName(int64_t InIndex) const;
    CString GetSelectedFrameDataParameterValue(int64_t InIndex) const;
    SEditorScriptValueReference GetSelectedFrameDataParameterReference(int64_t InIndex) const;
    bool HasSelectedFrameDataParameterChildren(int64_t InIndex) const;

    int64_t GetSelectedFrameDataLocalVariableNum() const;
    CString GetSelectedFrameDataLocalVariableName(int64_t InIndex) const;
    CString GetSelectedFrameDataLocalVariableValue(int64_t InIndex) const;
    SEditorScriptValueReference GetSelectedFrameDataLocalVariableReference(int64_t InIndex) const;
    bool HasSelectedFrameDataLocalVariableChildren(int64_t InIndex) const;

    void RequestReferenceChildren(const SEditorScriptValueReference & InReference);

    RFIELD()
    TScriptDelegate<void(const SEditorScriptValueReference & /*reference*/
        ,const TVector<SEditorScriptValue>& /*children*/
        )>  OnReferenceChildrenReceived;

protected:
    SEditorScriptFrameData SelectedFrameData;



//debug:
public:

    void Launch(uint16_t Port);
    void AttachTo(uint16_t Port);
    void Pause();
    void Continue();
    void Stop();

    RFIELD()
    TScriptDelegate<void()> OnDebugStarted;

    RFIELD()
    TScriptDelegate<void()> OnDebugStopped;

    EDebugClientState GetClientState() const { return ClientState; }

    RFIELD()
    TScriptDelegate<void(const CString&)> OnProjectPrint;
protected:
    EDebugClientState ClientState=EDebugClientState::NotRunning;

    //编辑器是tcp客户端，目标应用是tcp服务器
    //这样做的好处是编辑器可以反复重新到目标应用
    //而不需要目标应用重启
    //如果目标应用是tcp客户端，那么应用就必须写复杂的重连逻辑,让应用变得复杂更难维护
    TThreadSafeSharedPtr<CTCPClient> Client;
    TSharedObjectPtr<RProcess> ProjectProcess;
    

    void ConnectToServer(uint16_t Port);

    //在工作线程中调用
    void OnDataReceived();

    std::mutex ReceivedDataMutex;
    TVector<uint8_t> RecvBuffer;

    void SendToServer(const CString& InMessage);


    void OnResponseAttach(const CString& InMessage);
    //void OnResponseDetach(const CString& InMessage);
    void OnResponseContinue(const CString& InMessage);

    void OnResponseProjectPrint(const CString& InMessage);

    void SyncAllBreakpoints();
    void SyncBreakpoints(const CString& InFileID);

    //{
    //    "FilePath": "xxx",
    //    "Breakpoints": [
    //        {"Line": 0, "bEnabled": true },
    //        {"Line": 2, "bEnabled": true },
    //    ]
    //}
    CString CreateFileBreakpointJson(CString FileID);

    struct SFileBreakpointState
    {
        CString FileID;
        
        //request is sent, but not yet received
        bool bIsWaitingBreakpointResponse=false;
        //require to sync breakpoints but is sysncing
        //so we need to sync later 
        bool bRequireSyncBreakpointsLater=false;
    };

    TMap<CString, SFileBreakpointState> FileBreakpointStates;

    void PostBreakpointAdded(const CString& FileID, int64_t InLine);
    void PostBreakpointRemoved(const CString& FileID, int64_t InLine);
    void PostBreakpointStateChanged(const CString& FileID, int64_t InLine);

    bool bDonotSyncBreakpoints=false;

    void OnResponseFileBreakpoint(const CString& InMessage);

    void OnResponseHitBreakpoint(const CString& InMessage);

    void OnPaused();
    //resume from paused
    void OnResumed();

    void OnResponseTrace(const CString &InMessage);

    void RequestLocalVariable();
    void OnResponseLocalVariables(const CString &InMessage);

    void ParseLocalVariableValue(std::shared_ptr<CJsonObjectNode> InNode, SEditorScriptValue &OutValue);

    void OnResponseReferenceChildren(const CString &InMessage);

    void OnProjectExited(int64_t , bool);
    void PostProjectExited();

    void OnProjectError();

    void Tick(float DeltaTime) override;
};